﻿// 2025/10/16: 首个版本
// 2025/10/29: 修正打开时Install按键无法启用问题

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Avalonia.Controls;
using Avalonia.Threading;
using ASEva;
using ASEva.UIAvalonia;
using ASEva.Utility;

namespace CommonSource
{
    partial class PluginInstaller : DialogPanel
    {
        public PluginInstaller() // For designer
        {
            InitializeComponent();
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
        }

        public PluginInstaller(InstallPluginLibraryInfo[] libs, InstallPluginDriverInfo[] drivers, WorkflowInstallPluginCallback callback, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(600, 300, 850, 600);
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = language["title"];

            libs = libs.OrderBy(lib => lib.Name).ToArray();
            drivers = drivers.OrderBy(driver => driver.Name).ToArray();

            var runner = new PluginInstallerRunner(driverLayout);
            var model = new PluginInstallerViewModel(language, libs, drivers, callback, runner);
            model.InitializeCallbacks();
            model.UpdateCanInstallLibrary();
            DataContext = model;

            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(10);
            timer.Tick += delegate
            {
                runner.OtherLoopOperations();
            };
            timer.Start();
        }

        public override void OnClosing()
        {
            timer?.Stop();
        }

        private LanguageSwitch language;
        private DispatcherTimer timer;
    }

    partial class PluginInstallerViewModel(LanguageSwitch language, InstallPluginLibraryInfo[] libs, InstallPluginDriverInfo[] drivers, WorkflowInstallPluginCallback callback, PluginInstallerRunner runner) : ObservableObject
    {
        // Library items /////////////////////////////////////////////////////////////////////////////////////////

        public partial class LibraryItem(InstallPluginLibraryInfo info, LanguageSwitch language) : ObservableObject
        {
            public String Title
            {
                get
                {
                    String displayText;
                    if (NewInstalled)
                    {
                        displayText = info.Name + language["installed"];
                    }
                    else if (info.Status == InstallPluginLibraryStatus.GUIUnsupported)
                    {
                        displayText = info.Name + language["gui-unsupported"];
                    }
                    else if (info.Status == InstallPluginLibraryStatus.AsyncUnsupported)
                    {
                        displayText = info.Name + language["async-unsupported"];
                    }
                    else if (info.Status == InstallPluginLibraryStatus.TooNew)
                    {
                        displayText = info.Name + language["too-new"];
                    }
                    else if (info.Status == InstallPluginLibraryStatus.TooOld)
                    {
                        displayText = info.Name + language["too-old"]; ;
                    }
                    else if (info.InstalledVersion != null)
                    {
                        var curPackVersionText = info.InstalledVersion == null ? language["unknown-version"] : info.InstalledVersion.ToString();
                        var targetPackVersionText = info.Version == null ? language["unknown-version"] : info.Version.ToString();

                        if (info.InstalledVersion != null && info.Version != null && curPackVersionText == targetPackVersionText)
                        {
                            displayText = info.Name + language.Format("reinstall", curPackVersionText);
                        }
                        else
                        {
                            displayText = info.Name + " (" + curPackVersionText + " >> " + targetPackVersionText + ")";
                        }
                    }
                    else
                    {
                        var targetPackVersionText = info.Version == null ? language["unknown-version"] : info.Version.ToString();
                        displayText = info.Name + " (" + targetPackVersionText + ")";
                    }
                    return displayText;
                }
            }

            [ObservableProperty]
            private bool isChecked = info.Status == InstallPluginLibraryStatus.OK && !getDefaultUncheck(info, language);

            private static bool getDefaultUncheck(InstallPluginLibraryInfo info, LanguageSwitch language)
            {
                bool result = false;
                if (info.Status == InstallPluginLibraryStatus.OK && info.InstalledVersion != null)
                {
                    var curPackVersionText = info.InstalledVersion == null ? language["unknown-version"] : info.InstalledVersion.ToString();
                    var targetPackVersionText = info.Version == null ? language["unknown-version"] : info.Version.ToString();

                    if (info.InstalledVersion != null && info.Version != null && curPackVersionText == targetPackVersionText)
                    {
                        result = true;
                    }
                    else
                    {
                        result = info.InstalledVersion != null && info.Version != null && info.Version < info.InstalledVersion;
                    }
                }
                return result;
            }

            partial void OnIsCheckedChanged(bool value)
            {
                CheckedChanged?.Invoke(this, EventArgs.Empty);
            }

            public event EventHandler CheckedChanged;

            [ObservableProperty]
            private bool isEnabled = info.Status == InstallPluginLibraryStatus.OK;

            [ObservableProperty]
            [NotifyPropertyChangedFor(nameof(Title))]
            private bool newInstalled = false;

            public String LibraryID => info.LibraryID;

            public PluginInstalledStatus InstalledStatus => info.InstalledStatus;
        }

        [ObservableProperty]
        private ObservableCollection<LibraryItem> libraryItems = new(libs.Select(info => new LibraryItem(info, language)));

        public void InitializeCallbacks()
        {
            foreach (var item in LibraryItems)
            {
                item.CheckedChanged += onLibraryItemCheckedChanged;
            }
        }

        private void onLibraryItemCheckedChanged(object sender, EventArgs e)
        {
            UpdateCanInstallLibrary();
        }
        
        public void UpdateCanInstallLibrary()
        {
            CanInstallLibrary = LibraryItems.Any(item => item.IsChecked && item.IsEnabled);
        }

        [ObservableProperty]
        private bool isFilterInstalled = false;

        partial void OnIsFilterInstalledChanged(bool value)
        {
            if (value) CanFilterEnabled = true;
            else IsFilterEnabled = CanFilterEnabled = false;

            foreach (var item in LibraryItems)
            {
                if (item.NewInstalled) continue;

                var checkShouldEnable = true;
                if (value) checkShouldEnable = item.InstalledStatus != PluginInstalledStatus.NotInstalled;

                item.IsEnabled = checkShouldEnable;
                if (!checkShouldEnable && item.IsChecked) item.IsChecked = false; 
            }
        }

        [ObservableProperty]
        private bool isFilterEnabled = false;

        partial void OnIsFilterEnabledChanged(bool value)
        {
            foreach (var item in LibraryItems)
            {
                if (item.NewInstalled) continue;

                var checkShouldEnable = true;
                if (value) checkShouldEnable = item.InstalledStatus == PluginInstalledStatus.InstalledAndEnabled;
                else if (IsFilterInstalled) checkShouldEnable = item.InstalledStatus != PluginInstalledStatus.NotInstalled;

                item.IsEnabled = checkShouldEnable;
                if (!checkShouldEnable && item.IsChecked) item.IsChecked = false; 
            }
        }

        [ObservableProperty]
        private bool canFilterEnabled = false;

        [ObservableProperty]
        private bool canInstallLibrary = false;

        [RelayCommand]
        private void SelectAll()
        {
            foreach (var item in LibraryItems)
            {
                if (item.IsEnabled) item.IsChecked = true;
            }
        }

        [RelayCommand]
        private void ClearAll()
        {
            foreach (var item in LibraryItems)
            {
                if (item.IsEnabled) item.IsChecked = false;
            }
        }

        [RelayCommand]
        private async Task InstallLibrary()
        {
            var errorMsgs = new List<String>();
            foreach (var item in LibraryItems)
            {
                if (!item.IsChecked) continue;

                var errorMsg = callback.InstallLibrary(item.LibraryID);
                if (errorMsg != null) errorMsgs.Add(errorMsg);

                item.IsChecked = true;
                item.IsEnabled = false;
                item.NewInstalled = true;
            }

            foreach (var errorMsg in errorMsgs)
            {
                await AgencyLocal.PopupError(errorMsg);
            }
        }

        // Driver items /////////////////////////////////////////////////////////////////////////////////////////

        public partial class DriverItem(InstallPluginDriverInfo info, LanguageSwitch language) : ObservableObject
        {
            public String Title
            {
                get
                {
                    if (!info.Attached)
                    {
                        return info.Name + language["not-attached"];
                    }
                    else
                    {
                        return info.Name;
                    }
                }
            }

            public String DriverID => info.DriverID;

            public String Name => info.Name;
        }

        [ObservableProperty]
        private ObservableCollection<DriverItem> driverItems = new(drivers.Select(info => new DriverItem(info, language)));

        [ObservableProperty]
        private DriverItem selectedDriverItem = null;

        [RelayCommand]
        private async Task RunInstaller()
        {
            var driver = SelectedDriverItem;
            var prepareResult = callback.PrepareDriver(driver.DriverID);
            var exePath = prepareResult.Item1;
            var errorMsg = prepareResult.Item2;
            if (errorMsg == null)
            {
                runner.InstallEnvDriver(exePath, driver.Name);
            }
            else
            {
                await AgencyLocal.PopupError(errorMsg);
            }
        }
    }

    class PluginInstallerRunner
    {
        public PluginInstallerRunner(Control driverLayout)
        {
            switch (ASEva.APIInfo.GetRunningOS())
            {
                case "windows":
                case "macos":
                    platform = new PlatformFunctionDefault();
                    break;
                case "linux":
                case "linuxarm":
                    platform = new PlatformFunctionLinux(driverLayout);
                    break;
            }
        }

        public void InstallEnvDriver(String exePath, String driverName)
        {
            platform?.InstallEnvDriver(exePath, driverName);
        }

        public void OtherLoopOperations()
        {
            platform?.OtherLoopOperations();
        }

        private PlatformFunction platform;

        private interface PlatformFunction
        {
            void InstallEnvDriver(String exePath, String driverName);
            void OtherLoopOperations();
        }

        private class PlatformFunctionDefault : PlatformFunction
        {
            public void InstallEnvDriver(String exePath, String driverName)
            {
                AgencyLocal.StartProcess(exePath);
            }

            public void OtherLoopOperations()
            { 
            }
        }

        private class PlatformFunctionLinux : PlatformFunction
        {
            public PlatformFunctionLinux(Control driverLayout)
            {
                this.driverLayout = driverLayout;
            }

            public void InstallEnvDriver(String exePath, String driverName)
            {
                if (exePath != null && exePath.EndsWith(".sh"))
                {
                    runner = new ScriptRunner(exePath);
                    driverLayout.IsEnabled = false;
                }
                else
                {
                    AgencyLocal.StartProcess(exePath);
                }
            }

            public void OtherLoopOperations()
            { 
                if (runner != null && runner.IsOver())
                {
                    driverLayout.IsEnabled = true;
                    runner = null;
                }
            }

            private ScriptRunner runner = null;
            private Control driverLayout;
        }

        private class ScriptRunner
        {
            public ScriptRunner(String targetShFile) // 绝对路径
            {
                target = targetShFile;
                thread = new Thread(workFunc);
                thread.Start();
            }

            private void workFunc(object obj)
            {
                var startInfo = new ProcessStartInfo();
                startInfo.FileName = "gnome-terminal";
                startInfo.Arguments = "--wait -- bash \"" + target + "\"";
                startInfo.WorkingDirectory = Path.GetDirectoryName(target);

                var process = Process.Start(startInfo);

                while (true)
                {
                    Thread.Sleep(100);
                    if (process.WaitForExit(1)) break;
                }

                isOver = true;
            }

            public bool IsOver()
            {
                return isOver;
            }

            private bool isOver = false;
            private Thread thread = null;
            private String target;
        }
    }
}